using System;
using System.Xml;
using System.Diagnostics;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using ICSharpCode.SharpZipLib.Zip;
namespace Builder
{
	public class BuildException: Exception
	{
	    public BuildException()
	    {
	    }
	    public BuildException(string message)
	    : base(message)
	    {
	    }
	    public BuildException(string message, Exception inner)
	    : base(message, inner)
	    {
	    }
	}
	class MainClass
	{
		static string tempDir;
		static string srcDir;
		static string dataDir;
		static char dirSep;
		public static System.Reflection.Assembly Engine;
		
		static void ReadZipEntry(ZipInputStream zip, string target)
		{
			FileStream sw = File.Create((target));
			int nBytes = 2048;
			byte[] data = new byte[2048];
			while((nBytes = zip.Read(data, 0, data.Length))  > 0)
			{
				sw.Write(data,0,nBytes);
			}
			sw.Close();
		}
			
		static void Init()
		{
			Console.WriteLine("Welcome to Gamadus C# Builder...");
			dirSep = Path.DirectorySeparatorChar;
			DirectoryInfo dir;
			if(dirSep == '\\')
			{
				Console.WriteLine("Using Windows");
				dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(System.IO.Path.Combine(System.Environment.CurrentDirectory,System.Environment.GetCommandLineArgs()[0])));
			}
			else
			{
				Console.WriteLine("Using Unix");
				dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(System.Environment.GetCommandLineArgs()[0]));
			}
			Console.WriteLine(dir.FullName);
			string startupPath = dir.FullName;
			System.IO.Directory.SetCurrentDirectory(startupPath);

			tempDir = Path.Combine(Path.Combine(startupPath, "temp"),"builder")+dirSep;
			dataDir = startupPath+dirSep;
			srcDir = Path.Combine(startupPath, "src")+dirSep;
			
			if(!Directory.Exists(tempDir))
				Directory.CreateDirectory(tempDir);
				
			Console.WriteLine("Temp directory will be "+tempDir);
			Console.WriteLine("Data directory will be "+dataDir);
			Console.WriteLine("Source directory will be "+srcDir);
		}
		public static void Main(string[] args)
		{
			try
			{
			if(args.Length < 2)
            {
            	Console.WriteLine("Syntax: Builder input.zip output.exe");
            	return;
            }
            
			Init();
			ZipInputStream zip;
			try
			{				zip = new ZipInputStream(new StreamReader(args[0]).BaseStream);
			}
			catch(System.IO.FileNotFoundException)
			{
				throw new BuildException("B000: "+args[0]+" doesn't exist.");
			}
			ZipEntry curEntry;
			
			//Create Resource Writer
			if(File.Exists(srcDir+"includes.resources"))
				File.Delete(srcDir+"includes.resources");
			ResourceGenerator resgen = new ResourceGenerator(srcDir+"includes.resources");
			
			while ((curEntry = zip.GetNextEntry()) != null) 
		    {
				//Console.WriteLine("Zip entry found: "+curEntry.Name);
				if(curEntry.Name == "game.xml")
				{
					ReadZipEntry(zip,tempDir+"game.xml");
				}
				else
				{
					//Save file to temp dir and write it to ressource file
					ReadZipEntry(zip,tempDir+curEntry.Name);
					resgen.Add(tempDir+curEntry.Name, curEntry.Name);
					File.Delete(tempDir+curEntry.Name);
				}
		    }
			zip.Close();
			resgen.Generate();
			if(!File.Exists(tempDir+"game.xml"))
			{
				throw new BuildException("B001: "+args[0]+" is not a valid Gamadus project (doesn't contain game.xml)");
			}
			
			XmlDocument _doc = new XmlDocument();
			_doc.Load(tempDir+"game.xml");

            //###########################################
            Console.WriteLine("Compiling "+args[0]+"...");
			
			//WRITE AND COMPILE EMPTY GAME THE PARSER CAN ACCESS WITH REFLECTION/
			Engine engine = new Engine();
			StreamWriter rw = new StreamWriter(srcDir+"Resources.cs", false); //resource writer
			StreamWriter ow = new StreamWriter(srcDir+"Objects.cs", false); //object writer
			rw.WriteLine("//THIS FILE IS GENERATED AUTOMATICALLY BY THE BUILDER\nusing System;\nnamespace GamadusEngine\n{\npublic partial class ResourceMgr\n{\npublic void LoadSettings()\n{\nGamadusEngine.Instance.GameSettings[\"AllowESC\"] = true;\nGamadusEngine.Instance.GameSettings[\"AllowF4\"] = false;\nGamadusEngine.Instance.GameSettings[\"AllowCloseButton\"] = true;\nGamadusEngine.Instance.GameSettings[\"StartFullScreen\"] = false;\n}\npublic class Resources : SingletonManager<Resources>\n{\npublic void Init()\n{\nthis.tmp = typeof(tmp);\nResourceMgr.Instance.ResourceTable.Add(\"tmp\", this.tmp);\nrmtmp = ResourceMgr.Instance.CreateRoom(\"rmtemp\");\nrmtmp.bgcolor = System.Drawing.Color.FromArgb(255, 0, 0, 0);\nrmtmp.persistent = false;\nrmtmp.width = 100;\n\nrmtmp.height = 100;\nrmtmp.speed = 30;\n}\npublic Type tmp;\npublic Room rmtmp;\n}\npublic void FillRoom(Room lev)\n{\n}\n}\n}");
            ow.WriteLine("//THIS FILE IS GENERATED AUTOMATICALLY BY THE BUILDER\n			namespace GamadusEngine\n			{\n			public class tmp : ObjParent\n			{\n			public tmp(int x, int y, Room lev) : base(x, y, lev)\n			{\n			this.visible = true;\n			this.depth = 0;\n			this.persistent = false;\n			}\n			}\n			}");
            rw.Close();
            ow.Close();
            File.Delete(dataDir+"tmp.dat");
            CompilerResults result = CompileGame(dataDir+"tmp.dat", false);
            
			if(result.Errors.HasErrors)
	        {
	            throw new BuildException("0010: couldn't load engine sources");
//	            foreach(CompilerError ce in result.Errors)
//	            {
//	                Console.WriteLine("  {0}", ce.ToString());
//	                Console.WriteLine();
//	            }
	            return;
	        }
	        engine.Ass = System.Reflection.Assembly.LoadFrom(dataDir+"tmp.dat");
            
           	rw = new StreamWriter(srcDir+"Resources.cs", false); //resource writer
			ow = new StreamWriter(srcDir+"Objects.cs", false); //object writer
			
			
			if(_doc.GetElementsByTagName("Settings").Count < 1)
        		throw new BuildException("B002: "+args[0]+" is not a valid Gamadus project (missing Settings tag)");
        	
        	rw.WriteLine("//THIS FILE IS GENERATED AUTOMATICALLY BY THE BUILDER");
        	rw.WriteLine("using System;\nnamespace GamadusEngine\n{\npublic partial class ResourceMgr\n{");
        	rw.WriteLine("public void LoadSettings()\n{");
        	
        	ow.WriteLine("//THIS FILE IS GENERATED AUTOMATICALLY BY THE BUILDER\nnamespace GamadusEngine\n{");

        	XmlNode setNode = _doc.GetElementsByTagName("Settings")[0];
        	//Durchlaufe alle Tags unter Settings
        	foreach(XmlNode node in setNode.ChildNodes)
			{
				//Gehe zum nächsten eintrag, wenn es kein Set-Tag ist
				if(node.Name != "Set") continue;
				
				string key = node.Attributes["name"].InnerText;
				string Value;
				if(node.ChildNodes.Count > 0)
					Value = ReadXmlValue(node.ChildNodes[0]);
				else
					Value = "\""+node.InnerText+"\"";
				rw.WriteLine("GamadusEngine.Instance.GameSettings[\""+key+"\"] = "+Value+";");
			}
			rw.WriteLine("}\npublic class Resources : SingletonManager<Resources>\n{\npublic void Init()\n{"); //end of LoadSettings Method, Resource class Init
			string resDeclaration = "";
			Hashtable instanceList = new Hashtable();
			
			//Get Resources
			if(_doc.GetElementsByTagName("Resources").Count < 1)
				throw new BuildException("B003: "+args[0]+" is not a valid Gamadus project (missing Resources tag)");
        	XmlNode resNode = _doc.GetElementsByTagName("Resources")[0];
			bool roomFound = false;
			List<string> neededKeys = new List<string>();
			Hashtable resList = new Hashtable();

			foreach(XmlNode node in resNode.ChildNodes)
			{
				string name = node.Attributes["name"].InnerText;
				resList.Add(name, node.Name);
				if(node.Name == "Room")
					roomFound = true;
			}
			//exit if no room found
			if(!roomFound)
			{
				throw new BuildException("B004: No room exists, project must at least have one room");
			}
			
			//echo all resources
			foreach(DictionaryEntry res in resList)
			{
				Console.WriteLine("Resource: "+res.Key+" ("+res.Value+")");
			}
			GMLParser.Init(resList, engine);
			
			foreach(XmlNode node in resNode.ChildNodes)
			{
				string name = node.Attributes["name"].InnerText;
				neededKeys.Clear();
				switch(node.Name)
				{
					case "Font":
					resDeclaration+="public Font "+name+";\n";
					rw.WriteLine(name+" = new Font();\nResourceMgr.Instance.ResourceTable.Add(\""+name+"\", "+name+");");
					neededKeys.Add("size");
					neededKeys.Add("file");
					foreach(XmlNode child in node.ChildNodes)
					{
						if(child.Name == "Set")
						{
							string key = child.Attributes["name"].InnerText;
							if(neededKeys.Contains(key))
							{
								neededKeys.Remove(key);
								string Value;
								if(child.ChildNodes.Count > 0)
									Value = ReadXmlValue(child.ChildNodes[0]);
								else
									Value = "\""+child.InnerText+"\"";
								rw.WriteLine(name+"."+key+" = "+Value+";");
							}
						}
					}
					if(neededKeys.Count > 0)
						throw new BuildException("B005: "+args[0]+" is not a valid Gamadus project ("+name+": wrong font settings)");
					break;
					
					case "Object":
					resDeclaration+="public Type "+name+";\n";
					rw.WriteLine("this."+name+" = typeof("+name+");\nResourceMgr.Instance.ResourceTable.Add(\""+name+"\", this."+name+");");
					Object obj = ParseController.Instance.CreateObject(name);
					string setString = "";
					neededKeys.Add("visible");
					neededKeys.Add("persistent");
					neededKeys.Add("depth");
					foreach(XmlNode child in node.ChildNodes)
					{
						if(child.Name == "Set")
						{
							string key = child.Attributes["name"].InnerText;
							if(neededKeys.Contains(key))
							{
								neededKeys.Remove(key);
								string Value;
								if(child.ChildNodes.Count > 0)
									Value = ReadXmlValue(child.ChildNodes[0]);
								else
									Value = "\""+child.InnerText+"\"";
								setString += "this."+key+" = "+Value+";\n";
							}
						}
						else if(child.Name == "Event")
						{
							string id = child.Attributes["id"].InnerText;
							string code = "";
							foreach(XmlNode action in child.ChildNodes)
							{
								if(action.Name == "Action")
								{
									string aset = action.Attributes["set"].InnerText;
									string aid = action.Attributes["id"].InnerText;
									if(aset == "Standard" && aid == "Code") //direct code action
									{
										if(action.ChildNodes.Count < 1)
											throw new ArgumentException("Argument of Code action missing");
										XmlNode arg = action.ChildNodes[0];
										string lang = arg.Attributes["lang"].InnerText;
										ParseController.Instance.ParseCode(obj,id,"//"+lang+" code:\n"+arg.InnerText+"\n");
										
										code += "//"+lang+" code:\n"+arg.InnerText+"\n";
									}
								}
							}
							//write actions
						}
					}
					if(neededKeys.Count > 0)
						throw new BuildException("B006: "+args[0]+" is not a valid Gamadus project ("+name+": wrong object settings)");
					
					obj.StartString += "\n"+setString+"}";
					break;
					
					case "Room":
					resDeclaration += "public Room "+name+";\n";
					roomFound = true;
					instanceList.Add(name, "");
					neededKeys.Add("bgcolor");
					neededKeys.Add("persistent");
					neededKeys.Add("width");
					neededKeys.Add("caption");
					neededKeys.Add("height");
					neededKeys.Add("speed");
					rw.WriteLine(name+" = ResourceMgr.Instance.CreateRoom(\""+name+"\");");
					foreach(XmlNode child in node.ChildNodes)
					{
						if(child.Name == "Set")
						{
							string key = child.Attributes["name"].InnerText;
							if(neededKeys.Contains(key))
							{
								neededKeys.Remove(key);
								string Value;
								if(child.ChildNodes.Count > 0)
									Value = ReadXmlValue(child.ChildNodes[0]);
								else
									Value = "\""+child.InnerText+"\"";
								rw.WriteLine(name+"."+key+" = "+Value+";");
							}
						}
						else if(child.Name == "Instance")
						{
							if(child.ChildNodes.Count < 1)
								throw new ArgumentException("Parsing error: Project contains at least one instance which hasn't an object");
							string inst = ReadXmlValue(child.ChildNodes[0]);
							instanceList[name] += "ResourceMgr.Instance.CreateInstance(Resources.Instance."+inst+", "+child.Attributes["x"].InnerText+", "+child.Attributes["y"].InnerText+", lev);\n";
						}
					}
					if(neededKeys.Count > 0)
						throw new BuildException("B007: "+args[0]+" is not a valid Gamadus project ("+name+": wrong room settings)");
					break;
				}
			}
			ow.WriteLine(ParseController.Instance.GetOutput());
			
			
			rw.WriteLine("}\n"+resDeclaration+"}\n");
			
			rw.WriteLine("public void FillRoom(Room lev)\n{");
			bool first = true;
			foreach(DictionaryEntry fillString in instanceList)
			{
				rw.Write(first ? "if" : "else if");
				first = false;
				rw.WriteLine("(lev == Resources.Instance."+fillString.Key+")\n{\n"+fillString.Value+"}");
			}
			rw.WriteLine("}\n}\n}"); // end of FillRoom method, ResourceManager class and namespace
			ow.WriteLine("}");
			rw.WriteLine("\n");
			ow.WriteLine("\n");
			rw.Close();
			ow.Close();
			
			File.Delete(tempDir+"game_temp.exe");
			engine = null;
			//COMPILE//
			ResourceGenerator runtimeRes = new ResourceGenerator(srcDir+"runtime.resources");
			runtimeRes.Add(dataDir+"SdlDotNet.dll", "SdlDotNet.dll");
			runtimeRes.Add(dataDir+"Tao.Sdl.dll", "Tao.Sdl.dll");
			runtimeRes.Generate();
			
			CompilerResults cr = CompileGame(tempDir+"game_temp.exe",true, srcDir+"includes.resources", srcDir+"runtime.resources");
			
	        if(cr.Errors.Count > 0)
	        {
	            foreach(CompilerError ce in cr.Errors)
	            {
	            	if(!ce.IsWarning)
	            	{
	            		throw new BuildException("NATIVE Line "+ce.Line+" "+ce.ErrorNumber+" ("+ce.ErrorText+")");
	            	}
	                Console.WriteLine("  {0}", ce.ToString());
	                Console.WriteLine();
	            }
	        }
			if(!File.Exists(tempDir+"game_temp.exe"))
		   	{
			   	throw new ArgumentException("Couldn't build "+args[0]);
		   	}
		   	File.Delete(args[1]);
		   	File.Copy(tempDir+"game_temp.exe", args[1]);
			//Mono.Merge.Driver.Execute(new string[] {"-out", args[1], "temp"+dirSep+"game_temp.exe", "data"+dirSep+"SdlDotNet.dll", "data"+dirSep+"Tao.Sdl.dll"});
			}
			catch(BuildException e)
			{
				Console.WriteLine("ERROR: "+e.Message);
			}
			finally
			{
				if(args.Length >= 2)
					Clean();
			}
		}
		static void Clean()
		{
			Console.Write("Cleanup...");
			foreach(string f in Directory.GetFiles(tempDir))
			{
				File.Delete(f);
			}
			Directory.Delete(tempDir);
			if(File.Exists(srcDir+"includes.resources"))
				File.Delete(srcDir+"includes.resources");
			Console.WriteLine("Done.");
		}
		static CompilerResults CompileGame(string target, bool debug, params string[] resources)
		{
			CSharpCodeProvider provider = new CSharpCodeProvider();
			CompilerParameters cp = new CompilerParameters();
			cp.IncludeDebugInformation = debug;
	        // Generate an executable instead of 
	        // a class library.
	        cp.GenerateExecutable = true;
			
	        // Specify the assembly file name to generate.
	        cp.OutputAssembly = target;
	        
	        //Specify referenced assemblies and embedded resources
	        
	        cp.ReferencedAssemblies.Add("System.Drawing.dll");
	        cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
	        cp.ReferencedAssemblies.Add(dataDir+"Tao.Sdl.dll");
	        cp.ReferencedAssemblies.Add(dataDir+"SdlDotNet.dll");
	        cp.ReferencedAssemblies.Add("System.dll");
	        
	        foreach(string res in resources)
	        {
	        	cp.EmbeddedResources.Add(res);
	        }
	        
	        // Save the assembly as a physical file.
	        cp.GenerateInMemory = false;
	    
	        // Set whether to treat all warnings as errors.
	        cp.TreatWarningsAsErrors = false;
	 		List<string> srcFileList = new List<string>();
	 		foreach(string srcFile in Directory.GetFiles(srcDir))
	 		{
	 			if(srcFile.EndsWith(".cs"))
	 				srcFileList.Add(srcFile);
	 		}
	        // Invoke compilation of the source file.
	        return provider.CompileAssemblyFromFile(cp, srcFileList.ToArray());
		}
		static string ReadXmlValue(XmlNode child)
		{
			if(child.Name == "Color")
			{
				byte cr, cg, cb, ca;
				ca = 255;
				cr = byte.Parse(child.Attributes["r"].InnerText);
				cg = byte.Parse(child.Attributes["g"].InnerText);
				cb = byte.Parse(child.Attributes["b"].InnerText);
				return "System.Drawing.Color.FromArgb("+ca+", "+cr+", "+cg+", "+cb+")";
			}
			if(child.Name == "Resource")
			{
				return child.Attributes["name"].InnerText;
			}
			if(child.Name == "Bool")
			{
				return bool.Parse(child.Attributes["value"].InnerText).ToString().ToLower();
			}
			if(child.Name == "Int")
			{
				return int.Parse(child.Attributes["value"].InnerText).ToString();
			}
			if(child.Name == "File")
			{
				return "\""+child.Attributes["filename"].InnerText+"\"";
			}
			if(child.Name == "String")
			{
				return "\""+child.Attributes["value"].InnerText+"\"";
			}
			return null;
		}
    }
}